All files / src/hooks useUsernameValidation.ts

0% Statements 0/43
0% Branches 0/37
0% Functions 0/6
0% Lines 0/41

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110                                                                                                                                                                                                                           
import { useState, useEffect, useCallback } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useTranslation } from 'react-i18next';
import { apiService } from '@/services/api';
import { useAuth } from '@/contexts/AuthContext';
 
interface UsernameValidationResult {
  available: boolean;
  message: string;
}
 
interface UseUsernameValidationOptions {
  debounceMs?: number;
  skipIfEmpty?: boolean;
}
 
export const useUsernameValidation = (
  username: string,
  options: UseUsernameValidationOptions = {}
) => {
  const { t } = useTranslation();
  const { debounceMs = 500, skipIfEmpty = true } = options;
  const { user } = useAuth();
  const [debouncedUsername, setDebouncedUsername] = useState(username);
 
  // Debounce the username input
  useEffect(() => {
    if (skipIfEmpty && !username.trim()) {
      setDebouncedUsername('');
      return;
    }
 
    const timer = setTimeout(() => {
      setDebouncedUsername(username);
    }, debounceMs);
 
    return () => clearTimeout(timer);
  }, [username, debounceMs, skipIfEmpty]);
 
  // Query to check username availability
  const { data, isLoading, error } = useQuery({
    queryKey: ['username-check', debouncedUsername, user?.role],
    queryFn: async (): Promise<UsernameValidationResult> => {
      if (!debouncedUsername.trim() || debouncedUsername.length < 3) {
        return {
          available: false,
          message: debouncedUsername.length < 3 && debouncedUsername.length > 0
            ? t('reseller.endUsers.validation.usernameMin')
            : t('login.usernamePlaceholder')
        };
      }
 
      // Choose endpoint: admin/reseller when logged, public when anonymous
      let endpoint: string;
      if (user?.role === 'admin') {
        endpoint = `/api/admin/users/check-username/${encodeURIComponent(debouncedUsername)}`;
      } else if (user?.role === 'reseller') {
        endpoint = `/api/reseller/users/check-username/${encodeURIComponent(debouncedUsername)}`;
      } else {
        endpoint = `/api/redeem/check-username/${encodeURIComponent(debouncedUsername)}`;
      }
 
      const result = await apiService.get<UsernameValidationResult>(endpoint);
      if (!result.success) {
        throw new Error(result.error.details || t('common.serverError'));
      }
      return result.data;
    },
    enabled: !!debouncedUsername.trim() && debouncedUsername.length >= 3,
    staleTime: 30000, // Cache for 30 seconds
    retry: false, // Don't retry on validation errors
  });
 
  // Determine the validation state
  const getValidationState = useCallback(() => {
    if (!debouncedUsername.trim()) {
      return { isValid: null, color: 'gray', message: '' };
    }
 
    if (isLoading) {
      return { isValid: null, color: 'blue', message: t('common.loading') };
    }
 
    if (error) {
      return { isValid: false, color: 'red', message: t('common.serverError') };
    }
 
    if (data) {
      return {
        isValid: data.available,
        color: data.available ? 'green' : 'red',
        message: data.message
      };
    }
 
    return { isValid: null, color: 'gray', message: '' };
  }, [debouncedUsername, isLoading, error, data, t]);
 
  const validationState = getValidationState();
 
  return {
    isAvailable: data?.available ?? null,
    isChecking: isLoading,
    message: validationState.message,
    color: validationState.color,
    isValid: validationState.isValid,
    error: error?.message
  };
};